// UDP hole punching example, server code
// Base UDP code stolen from http://www.abc.se/~m6695/udp.html
// By Oscar Rodriguez
// This code is public domain, but you're a complete lunatic
// if you plan to use this code in any real program.
 
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
 
#define BUFLEN 512
#define NPACK 10
#define PORT 9930
 
// A small struct to hold a UDP endpoint. We'll use this to hold each client's endpoint.
typedef struct
{
    int id;
    int host;
    short port;
} ClientIP;

typedef struct
{
    int type;
    int client_id;
    int dataLen;
} TransHeader;

typedef struct
{
    int dataLen;
    TransHeader mheader;
    ClientIP cip;
    char data[100];
} TransData;
 
// Just a function to kill the program when something goes wrong.
void diep(char *s)
{
    perror(s);
    exit(1);
}

/*
    - htons
    函数原型：uint16_t htons(uint16_t hostshort)
    功能：将16位无符号整数（主机字节序）转换为网络字节序（大端字节序）。
    参数：hostshort为要转换的16位无符号整数。
    返回值：返回转换后的网络字节序（大端字节序）的16位无符号整数。
*/

/*
    - htonl
    函数原型：uint32_t htonl(uint32_t hostlong)
    功能：将32位无符号整数（主机字节序）转换为网络字节序（大端字节序）。
    参数：hostlong为要转换的32位无符号整数。
    返回值：返回转换后的网络字节序（大端字节序）的32位无符号整数。
*/

/*
    inet_ntoa函数：
    函数原型：char *inet_ntoa(struct in_addr in);
    功能：将32位网络字节序的IP地址转换为点分十进制形式的字符串表示。
    参数：in为struct in_addr类型的结构体，其中存储了32位网络字节序的IP地址。
    返回值：返回一个指向表示IP地址的字符串的指针。

    例如，可以使用如下代码将32位网络字节序的IP地址转换为点分十进制形式的字符串：
    ```
    struct in_addr addr;
    addr.s_addr = htonl(ipAddress);  // 假设ipAddress是32位网络字节序的IP地址
    char* ipStr = inet_ntoa(addr);
    printf("IP Address: %s\n", ipStr);
    ```
    在这个例子中，`inet_ntoa`函数将`addr`中的32位网络字节序的IP地址转换为点分十进制形式的字符串，并将结果存储在`ipStr`中。


    ntohs函数：
    函数原型：uint16_t ntohs(uint16_t netshort);
    功能：将16位网络字节序的端口号转换为主机字节序的端口号。
    参数：netshort为16位网络字节序的端口号。
    返回值：返回转换后的主机字节序的端口号。


    ntohl函数：
    函数原型：uint32_t ntohl(uint32_t netlong);
    功能：将32位网络字节序的整数转换为主机字节序的整数。
    参数：netlong为32位网络字节序的整数。
    返回值：返回转换后的主机字节序的整数。
    ``ntohl`函数用于将32位网络字节序的整数转换为主机字节序的整数。它通常用于解析网络数据中的整数值，以便在主机上进行处理和显示。

*/

int main(void)
{
    struct sockaddr_in si_me, si_other;
    int s, i, j, slen=sizeof(si_other);
    char buf[BUFLEN];
    ClientIP clients[10]; // 10 clients. Notice that we're not doing any bound checking.

    TransData *p_recvTransData = (TransData*)malloc(sizeof(TransData));

    // struct client demo;
    int n = 0;
 
    // Create a UDP socket
    if ((s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1)
        diep("socket");
 
    // si_me stores our local endpoint. Remember that this program
    // has to be run in a network with UDP endpoint previously known
    // and directly accessible by all clients. In simpler terms, the
    // server cannot be behind a NAT.
    memset((char *) &si_me, 0, sizeof(si_me));
    si_me.sin_family = AF_INET;
    si_me.sin_port = htons(PORT); // 转换为网络字节序
    si_me.sin_addr.s_addr = htonl(INADDR_ANY); // 转换为网络字节序
    if (bind(s, (struct sockaddr*)(&si_me), sizeof(si_me))==-1)
        diep("bind");
 
    while (1)
    {
        // When a new client sends a datagram...
        if (recvfrom(s, p_recvTransData, sizeof(TransData), 0, (struct sockaddr*)(&si_other), &slen)==-1)
            diep("recvfrom");
        // The client's public UDP endpoint data is now in si_other.
        // Notice that we're completely ignoring the datagram payload.
        // If we want to support multiple clients inside the same NAT,
        // we'd have clients send their own private UDP endpoints
        // encoded in some way inside the payload, and store those as
        // well.
        printf("Received packet from %s:%d\n", inet_ntoa(si_other.sin_addr), ntohs(si_other.sin_port));
        printf("Received packet is %s\n", p_recvTransData->data);
        printf("Received packet id is %d\n", p_recvTransData->mheader.client_id);

        // Now we add the client's UDP endpoint in our list.
        clients[n].host = si_other.sin_addr.s_addr;
        clients[n].port = si_other.sin_port;
        clients[n].id = 6666;
        n++;
        // And then tell everybody about everybody's public UDP endpoints
        for (i = 0; i < n; i++)
        {
            si_other.sin_addr.s_addr = clients[i].host;
            si_other.sin_port = clients[i].port;
            // We send a datagram for each client in our list. Of course,
            // we could also assemble a single datagram and send that.
            for (j = 0; j < n; j++)
            {
                // The payload is the client's public UDP endpoint, clients[j]
                printf("Sending to %s:%d\n", inet_ntoa(si_other.sin_addr), ntohs(si_other.sin_port));
                // We're sending binary data here, using the server's byte order.
                // In your code, you should make sure every client agrees on the endianness.

                TransData sendData;
                ClientIP clientIP;
                memset(&sendData, 0, sizeof(TransData));

                clientIP.host = clients[j].host;
                clientIP.port = clients[j].port;
                clientIP.id = clients[j].id;

                sendData.cip = clientIP;
                sendData.dataLen = sizeof(TransData);
                //sendData.data = "This is server message!";
                char send_message[] = "This is server message!";
                strncpy(sendData.data, send_message, sizeof(send_message));

                if (sendto(s, &sendData, sizeof(TransData), 0, (struct sockaddr*)(&si_other), slen)==-1)
                    diep("sendto");
            }
        }
        printf("Now we have %d clients\n", n);
        // And we go back to listening. Notice that since UDP has no notion
        // of connections, we can use the same socket to listen for data
        // from different clients.
    }
 
    // Actually, we never reach this point...
    close(s);
    return 0;
}